package cc.shacocloud.luanniao.web.service.impl


import cc.shacocloud.luanniao.core.model.constant.UserAuthRoleEnum
import cc.shacocloud.luanniao.core.model.po.UserPo
import cc.shacocloud.luanniao.core.service.UserService
import cc.shacocloud.luanniao.core.service.cache.CacheManager
import cc.shacocloud.luanniao.core.exception.ForbiddenException
import cc.shacocloud.luanniao.core.exception.UserStatusAbnormalException
import cc.shacocloud.luanniao.web.config.CacheProperties
import cc.shacocloud.luanniao.web.model.mo.NotLoggedUserSessionMo
import cc.shacocloud.luanniao.web.model.mo.SessionMo
import cc.shacocloud.luanniao.web.model.mo.UserSessionMo
import cc.shacocloud.luanniao.web.service.SessionService
import cc.shacocloud.luanniao.web.service.SessionService.Companion.SESSION_NAME
import kotlinx.serialization.json.Json.Default.serializersModule
import kotlinx.serialization.serializer
import org.slf4j.LoggerFactory
import org.springframework.http.ResponseCookie
import org.springframework.http.server.reactive.ServerHttpResponse
import org.springframework.stereotype.Service
import org.springframework.web.reactive.socket.WebSocketSession
import org.springframework.web.server.ServerWebExchange
import kotlin.time.Duration
import kotlin.time.DurationUnit
import kotlin.time.toJavaDuration

/**
 * @author 思追(shaco)
 */
@Service
class SessionServiceImpl(
    cacheManager: CacheManager,
    cacheProperties: CacheProperties,
    val userService: UserService,
) : SessionService {

    val sessionCache = cacheManager.getCache(cacheProperties.userSession)

    companion object {

        val SESSION_SERIALIZER = serializersModule.serializer<SessionMo>()

        /**
         * 会话携带在请求中的属性键
         */
        val SESSION_ATTRIBUTE_KEY = "${SessionServiceImpl::class.java.canonicalName}.session"

        private val log = LoggerFactory.getLogger(SessionServiceImpl::class.java)
    }

    /**
     * 保存会话信息
     * @param sessionMo 用户会话信息
     * @param ttl 会话过期事件
     */
    override suspend fun saveSession(
        response: ServerHttpResponse,
        sessionMo: SessionMo,
        ttl: Duration,
    ) {
        sessionCache.put(sessionMo.sessionId, SESSION_SERIALIZER, sessionMo, ttl)

        // 添加 cookie
        val cookie = ResponseCookie.from(SESSION_NAME, sessionMo.sessionId)
            .path("/")
            .maxAge(ttl.toLong(DurationUnit.SECONDS))
            .build()
        response.addCookie(cookie)
    }

    /**
     * 移除会话
     */
    override suspend fun removeSession(
        response: ServerHttpResponse,
        sessionMo: SessionMo,
    ) {
        sessionCache.del(sessionMo.sessionId)

        // 添加立即过期的 cookie
        val cookie = ResponseCookie.from(SESSION_NAME, sessionMo.sessionId)
            .path("/")
            .maxAge(0)
            .build()
        response.addCookie(cookie)
    }

    /**
     * 获取当前访问用户的会话信息
     */
    override suspend fun currentSession(exchange: ServerWebExchange): SessionMo {
        return exchange.attributes[SESSION_ATTRIBUTE_KEY] as SessionMo? ?: let {

            val sessionMo = exchange.request.cookies[SESSION_NAME]?.let {
                it.firstNotNullOfOrNull { cookie -> sessionCache.get(cookie.value, SESSION_SERIALIZER) }
            } ?: NotLoggedUserSessionMo()

            exchange.attributes[SESSION_ATTRIBUTE_KEY] = sessionMo

            sessionMo
        }
    }

    override suspend fun currentSession(session: WebSocketSession): SessionMo {
        return session.handshakeInfo.cookies[SESSION_NAME]?.let {
            it.firstNotNullOfOrNull { cookie -> sessionCache.get(cookie.value, SESSION_SERIALIZER) }
        } ?: NotLoggedUserSessionMo()
    }

    /**
     * 获取当前用户信息
     */
    override suspend fun getCurrentUser(exchange: ServerWebExchange): UserPo {
        val userId = getCurrentUserId(exchange)
        return userService.findById(userId) ?: throw UserStatusAbnormalException()
    }

    /**
     * 获取当前用户id
     */
    override suspend fun getCurrentUserId(exchange: ServerWebExchange): Long {
        return getUserSession(exchange).userId
    }

}